package src

import (
	"encoding/json"
	"fmt"
	"math"
	"strconv"
	"strings"
	"time"

	"gitee.com/haifengat/goctp"
	ctp "gitee.com/haifengat/goctp/lnx"
	"github.com/sirupsen/logrus"
)

var execTicks = 0

func startQuote() {
	logrus.Info("connect to quote ...")

	var chConnected = make(chan bool)
	q.RegOnFrontConnected(func() {
		logrus.Infoln("quote connected")
		chConnected <- true
		q.ReqLogin(investorID, password, brokerID)
	})
	q.RegOnFrontDisConnected(func(reason int) {
		logrus.Info("quote disconected ", reason)
		if reason != 0 {
			releaseQuote()
		}
	})
	q.RegOnRspUserLogin(onMdLogin)
	q.RegOnTick(func(tick *goctp.TickField) { onTick(*tick) }) // 转为 struct 以释放指针

	q.ReqConnect(quoteFront)
	go func() {
		// 连接超时设置
		select {
		case <-chConnected:
		case <-time.After(10 * time.Second):
			fmt.Println("连接超时")
			releaseQuote()
		}
	}()
}

func releaseQuote() {
	q.Release()
	q = ctp.NewQuote()
}

func onTick(tick goctp.TickField) {
	if tick.LastPrice >= math.MaxFloat32 {
		return
	}
	// 合约状态过滤 == 会造成入库延时
	if status, loaded := mapInstrumentStatus.Load(tick.InstrumentID); !loaded || status.(*goctp.InstrumentStatus).InstrumentStatus != goctp.InstrumentStatusContinous {
		return
	}
	// 取tick的分钟构造当前分钟时间
	var action = t.TradingDay
	// 夜盘
	hour, _ := strconv.Atoi(tick.UpdateTime[0:2])
	if hour <= 3 {
		action = actionDayNext
	} else if hour >= 20 {
		action = actionDay
	}

	minDateTime := fmt.Sprintf("%s-%s-%s %s:00", action[0:4], action[4:6], action[6:], tick.UpdateTime[0:5])

	if obj, loaded := instLastMin.LoadOrStore(tick.InstrumentID, &Bar{ID: minDateTime, InstrumentID: tick.InstrumentID, PreVol: tick.Volume, OpenInterest: tick.OpenInterest, Ticks: 1, TradingDay: t.TradingDay, Open: tick.LastPrice, High: tick.LastPrice, Low: tick.LastPrice, Close: tick.LastPrice, Volume: 0}); loaded {
		var bar = obj.(*Bar)
		minDiff := strings.Compare(minDateTime, bar.ID)
		// 不处理 <0 的情况
		if minDiff < 0 {
			return
		}
		if minDiff > 0 { // 新的分钟
			preVol := bar.PreVol + bar.Volume
			bar = &Bar{
				ID:           minDateTime,
				InstrumentID: tick.InstrumentID,
				PreVol:       preVol,
				OpenInterest: tick.OpenInterest,
				Ticks:        1,
				TradingDay:   t.TradingDay,
				Open:         tick.LastPrice,
				High:         tick.LastPrice,
				Low:          tick.LastPrice,
				Close:        tick.LastPrice,
				Volume:       tick.Volume - preVol,
			}
		} else { // 分钟数据更新
			const E = 0.000001
			if tick.LastPrice-bar.High > E {
				bar.High = tick.LastPrice
			}
			if tick.LastPrice-bar.Low < E {
				bar.Low = tick.LastPrice
			}
			bar.Close = tick.LastPrice
			bar.Volume = tick.Volume - bar.PreVol
			bar.OpenInterest = tick.OpenInterest

			// 此时间是否 push过
			if rdb != nil && bar.Volume > 0 { // 过滤成交量==0的数据
				bar.Ticks++
				// 当前分钟未被记录
				if bar.Ticks == 3 { // 控制分钟最小tick数量；避免盘歇的数据
					jsonStr, _ := json.Marshal(&bar)
					err := rdb.RPush(ctx, tick.InstrumentID, jsonStr).Err()
					if err != nil {
						logrus.Errorf("redis rpush error: %s %v", tick.InstrumentID, err)
					}
					// 发布分钟数据
					rdb.Publish(ctx, "md."+tick.InstrumentID, jsonStr)
				} else if bar.Ticks > 3 {
					jsonStr, _ := json.Marshal(&bar)
					err := rdb.LSet(ctx, tick.InstrumentID, -1, jsonStr).Err()
					if err != nil {
						logrus.Errorf("redis lset error: %s %v", tick.InstrumentID, err)
					}
					// 发布分钟数据
					rdb.Publish(ctx, "md."+tick.InstrumentID, jsonStr)
				}
			}
		}
		instLastMin.Store(tick.InstrumentID, bar) // 更新 bar
	}
	execTicks++
}

func onMdLogin(login *goctp.RspUserLoginField, info *goctp.RspInfoField) {
	logrus.Infoln("quote login:", info)
	// q.ReqSubscript("au2012")
	t.Instruments.Range(func(k, v interface{}) bool {
		// 取最新K线数据
		inst := k.(string)
		if jsonMin, err := rdb.LRange(ctx, inst, -1, -1).Result(); err == nil && len(jsonMin) > 0 {
			var bar = new(Bar)
			if json.Unmarshal([]byte(jsonMin[0]), bar) == nil {
				instLastMin.Store(inst, bar)
			}
		}
		// 更新合约状态
		pid := v.(*goctp.InstrumentField).ProductID
		if len(pid) > 0 {
			if status, loaded := t.InstrumentStatuss.Load(pid); loaded {
				mapInstrumentStatus.Store(k, status)
			}
		}
		return true
	})
	i := 0
	ps := len(products)
	// 订阅行情
	var insts = make([]string, 0)
	t.Instruments.Range(func(k, v interface{}) bool {
		if len(v.(*goctp.InstrumentField).ProductID) == 0 { // 过滤 非正常合约
			return true
		}
		if ps > 0 { // 如果设置了接收的品种
			// 大写比较
			p := strings.ToUpper(v.(*goctp.InstrumentField).ProductID)
			if len(p) == 0 { // BUK等组合合约productid为""
				return true
			}
			idx := -1
			for j, v := range products {
				if strings.Compare(strings.ToUpper(v), p) == 0 {
					idx = j
					break
				}
			}
			if idx == -1 { // 不在列表里
				return true
			}
		}
		insts = append(insts, k.(string))
		if len(insts) > 200 { // 一次订阅 200个合约
			q.ReqSubscript(insts...)
			insts = make([]string, 0)
		}
		// q.ReqSubscript(k.(string))
		// if i%1000 == 0 { // 网络限制,重复订阅次数过多后崩溃 exit with status -13
		// 	time.Sleep(1 * time.Second)
		// }
		i++
		return true
	})
	if len(insts) > 0 {
		q.ReqSubscript(insts...)
	}
	logrus.Infof("subscript instrument count: %d", i)
}
